//
//  ChatElements.m
//  SolarSeek
//
//  Created by Erick Fejta on 05/05/25.
//  Copyright 2005 __MyCompanyName__. All rights reserved.
//

#import "ChatElements.h"
#import "SolarSeekApplication.h"
#import "Chats.h"
#import "ChatsController.h"
#import "ChatView.h"
#import "ChatsView.h"
#import "ApplicationDefaults.h"

ChatsElement *ElementChatsParent=nil;

typedef enum { ObserveStatements=0, ObserveJoined=1, ObserveParted } Observing;

@implementation ChatElement
- (id)initWithView:(NSView *)view chat:(Chat *)chat icon:(NSImage *)icon {
	if(self = [super initWithView:view]) {
		_last = nil;
		_lastMe = [[NSDate alloc] init];
		_conversations = [[NSArray alloc] initWithObjects:chat,nil];
		_text = [[NSMutableAttributedString alloc] init];
		[chat addObserver:self forKeyPath:@"statements" options:nil context:nil];
		joined = NO;
		if([chat type]==ChatTypeRoom) {
			[chat addObserver:self forKeyPath:@"room.joined" options:nil context:nil];
			[chat addObserver:self forKeyPath:@"peerJoined" options:nil context:(void *)ObserveJoined];
			[chat addObserver:self forKeyPath:@"peerParted" options:nil context:(void *)ObserveParted];
		}
			
	}
	return self;
}

//- (id)autorelease { return [super autorelease]; }
//- (oneway void)release { return [super release]; }

- (void)dealloc {
	[_lastMe release];
	[_last release];
	[[self chat] removeObserver:self forKeyPath:@"statements"];
	if([self isSelected]) {
		[[[self chat] peersJoined] removeObserver:self forKeyPath:@"values"];
	}
	if([[self chat] type]==ChatTypeRoom) {
		Chat *chat = [self chat];
		[chat removeObserver:self forKeyPath:@"room.joined"];
		[chat removeObserver:self forKeyPath:@"peerJoined"];
		[chat removeObserver:self forKeyPath:@"peerParted"];
	}
	[_conversations release];
	[_text release];
	[super dealloc];
}

- (unsigned)hash { return [[self chat] hash]; }
- (BOOL)isEqual:(id)obj { return [obj isKindOfClass:[ChatElement class]] && [[self chat] isEqual:[obj chat]]; }


- (Chat *)chat { return [_conversations lastObject]; }

- (NSAttributedString *)text { return _text; }
- (NSArray *)conversations { return _conversations; }
- (NSArray *)participants { return [[[self chat] peersJoined] values]; }

- (void)removeFromSourceList {
	[NSApp selectSourceListElement:[ChatsElement elementChatsRoot]];
	[[[NSApp engine] chatsController] performSelector:@selector(endChat:) withObject:[self chat] afterDelay:0.0];
	[[[ChatsElement elementChatsRoot] view] performSelector:@selector(saveChats) withObject:nil afterDelay:0.0];
}

- (void)setSelected:(BOOL)selected {
	@synchronized(self) {
		BOOL old = [self isSelected];
		[super setSelected:selected];
		if(selected & (old^selected) ) {	//selected now.
			[[(ChatView *)[self view] textStorage] setAttributedString:_text];
			[[[self chat] peersJoined] addObserver:self forKeyPath:@"values" options:nil context:nil];
			[self update];
			ChatView *cv = [self view];
			NSString *title;
			switch([[self chat] type]) {
				case ChatTypePrivate: 
					title = [@"Chatting with " stringByAppendingString:[[[self chat] peer] name]];
					break;
				case ChatTypeRoom:
				default:
					title = [@"Chatting in " stringByAppendingString:[[[self chat] room] name]];
			}
			[[cv textView] setName:title];
			[cv scrollToBottom];	//this also changes the first responder.
			[cv updateFirstResponder];
			[self refresh];
			[[(ChatView *)[self view] textView]  animateName];
		}
		else if(old & (old^selected)) { //not selectd now.
			[[[self chat] peersJoined] removeObserver:self forKeyPath:@"values"];
			[self refresh];
		}
	}
}

- (NSFont *)fontUserName {
	return [NSFont boldSystemFontOfSize:[[NSUserDefaults standardUserDefaults] floatForKey:SolarSeekDefaultSizeFontChatText]];
}

- (NSFont *)fontStatement {
	return [NSFont systemFontOfSize:[[NSUserDefaults standardUserDefaults] floatForKey:SolarSeekDefaultSizeFontChatText]];
}

- (NSFont *)fontInformation {
	float f = [[NSUserDefaults standardUserDefaults] floatForKey:SolarSeekDefaultSizeFontChatText];
	if(f==0.0)
		f = [NSFont systemFontSize]-0.5;
	return [NSFont systemFontOfSize:f];
}


- (NSAttributedString *)attributedStringForInformationStatement:(NSString *)string {
	NSFont *f = [self fontInformation];
	NSMutableParagraphStyle *mps = [[NSMutableParagraphStyle alloc] init];
	[mps setAlignment:NSCenterTextAlignment];
	NSAttributedString *as = [[[NSAttributedString alloc] initWithString:string
		attributes:[NSDictionary dictionaryWithObjectsAndKeys:
			f,NSFontAttributeName,
			mps,NSParagraphStyleAttributeName,
			[NSColor grayColor],NSForegroundColorAttributeName,
			nil]] autorelease];
	[mps release];
	return as;
}

- (NSAttributedString *)attributedStringForDate:(NSCalendarDate *)date printDayOfWeek:(BOOL)print {
	NSString *f = print?@"%A %X\n":@"%X\n";
	NSString *str = [date descriptionWithCalendarFormat:f];
	return [self attributedStringForInformationStatement:str];
}


- (void)appendInformationStatement:(NSString *)string {
	if([[NSUserDefaults standardUserDefaults] boolForKey:SolarSeekDefaultShowInformationMessages]) {
		string = [string stringByAppendingString:@"\n"];
		NSAttributedString *as = [self attributedStringForInformationStatement:string];
		@synchronized(self) {
			if([self isSelected]) {
				[self willChangeValueForKey:@"text"];
				@try {
					BOOL scroll = [(ChatView *)[self view] shouldScrollToBottomAfterAppending];
					[_text appendAttributedString:as];
					[[(ChatView *)[self view] textStorage] appendAttributedString:as];
					if(scroll)
						[(ChatView *)[self view] scrollToBottom];
				}
				@catch(id ue) {
					NSLog(@"Uncaught exception appending info statement: %@",ue);
				}
				@finally {
					[self didChangeValueForKey:@"text"];
				}
			}
		}
	}
//	NSLog(@"Appended %@",string);
}

- (void)appendJoinedMessage {
	if([[self chat] type] == ChatTypeRoom) {
		[self appendInformationStatement:
			[NSString stringWithFormat:@"%@ %@",[[[self chat] room] joined]?@"Joined":@"Left",[[[self chat] room] name]]];
	}
}

- (void)update {
	unsigned neu;
	@synchronized([self chat]) {
		neu  = [[self chat] countOfStatementsNew];
		if([[NSUserDefaults standardUserDefaults] boolForKey:SolarSeekDefaultAnimateIconForNewPrivateChat]) {
			if([[self chat] type]==ChatTypePrivate || [_lastMe timeIntervalSinceNow] > -300)
				[NSApp requestUserAttention:NSInformationalRequest];
			else if(neu && [_lastMe timeIntervalSinceNow] < -900) {
				Statement *newest = [[[self chat] statements] lastObject];
				[[newest date] retain];
				[_lastMe release];
				_lastMe = [newest date];
				[NSApp requestUserAttention:NSInformationalRequest];
			}
		}
		if([[self chat] type]==ChatTypeRoom && joined != [[[self chat] room] joined]) {
			joined = [[[self chat] room] joined];
			[self appendJoinedMessage];
		}
		
		if(neu==0 || [self isSelected]==NO)	{
			[self refresh];
			return;
		}
		[[(ChatView *)[self view] textView] animateNameWithMaxAlpha:0.3];
		[[self chat] setCountOfStatementsNew:0];
	}
	unsigned more = neu;
	NSEnumerator *e = [[[self chat] statements] reverseObjectEnumerator];
	Statement *s;
	NSMutableArray *a = [NSMutableArray arrayWithCapacity:more];
	BOOL scroll = [(ChatView *)[self view] shouldScrollToBottomAfterAppending];
	NSCalendarDate *biggerDate = nil;
	NSCalendarDate *biggestDate = nil;
	Statement *newestmy = nil;
	while((s = [e nextObject]) && more--) {	//remember we're going backwards.
		if(newestmy==nil && [[s peer] isEqual:[[NSApp engine] me]]) {
			newestmy = s;
		}
		//decide if there was a big enough gap between the big and small gap, print a message.
		NSCalendarDate *smallerDate = [[NSCalendarDate alloc] initWithString:[[s date] description]]; //keep ownership
		if(biggerDate) {
			NSTimeInterval interval = [biggerDate timeIntervalSinceDate:smallerDate];
			interval-=([[self chat] type]==ChatTypeRoom)?300:200;
			if(interval > 0) {
				[a addObject:[self attributedStringForDate:biggerDate
						printDayOfWeek:[biggerDate dayOfCommonEra]!=[smallerDate dayOfCommonEra]]];
			}
			[biggerDate release];	//now longer owning.
		}
		else
			biggestDate = [smallerDate retain];
		biggerDate = smallerDate;	//still retained.
		
		//print the statement.
		NSMutableAttributedString *mas=nil;
		NSString *statement = [s statement];
		//don't use a picture if we have /me
		if([statement length] > 4 && [[statement substringToIndex:3] isEqual:@"/me"]) {
			statement = [statement substringFromIndex:4];
			mas = [[NSMutableAttributedString alloc] initWithString:@""];
		}
		else {	//use the buddy icon otherwise
			NSFileWrapper *wrap = [[NSFileWrapper alloc] init];
			[wrap setPreferredFilename:@"buddyicon"];
			NSImage *img = [[s peer] icon];
			if(img==nil) img = [NSImage imageNamed:@"chatToolbar"];
			[img setScalesWhenResized:YES];
			[img setSize:NSMakeSize(24,24)];
			[wrap setIcon:img];
			NSTextAttachment *att = [[NSTextAttachment alloc] initWithFileWrapper:wrap];
			mas = [[NSMutableAttributedString alloc] initWithAttributedString:[NSAttributedString attributedStringWithAttachment:att]];
			[att release];
			[wrap release];
		}
		
		//name part
		[mas appendAttributedString:[[[NSAttributedString alloc] 
			initWithString:[NSString stringWithFormat:@" %@ ",[[s peer] name]]
			attributes:[NSDictionary dictionaryWithObjectsAndKeys:
				[self fontUserName],NSFontAttributeName,nil]
			] autorelease]];
			
		//statement part
		[mas appendAttributedString:[[[NSAttributedString alloc] 
			initWithString:statement 
			attributes:[NSDictionary dictionaryWithObjectsAndKeys:[self fontStatement],NSFontAttributeName,nil]
			] autorelease]];
			
		//cleanup
		[mas appendAttributedString:[[[NSAttributedString alloc] initWithString:@"\n"] autorelease]];
		[a addObject:mas];
		[mas release];
	}
	
	NSTimeInterval elapsed = 0;
	if(biggerDate && _last) {
		elapsed = [biggerDate timeIntervalSinceDate:_last];
		elapsed-=([[self chat] type]==ChatTypeRoom)?300:200;
	}
	if(_last == nil || elapsed > 0) {
		BOOL pdow = _last==nil;
		if(_last && biggerDate && elapsed > 0)
			pdow = [biggerDate dayOfCommonEra]!=[_last dayOfCommonEra];
		[a addObject:[self attributedStringForDate:biggerDate printDayOfWeek:YES]];
	}
	[_last release];
	_last = biggestDate; //retained earlier
	[biggerDate release];  //no longer want ownership (do after retaining for _last).
	
	NSAttributedString *as;
	e = [a reverseObjectEnumerator];
	NSMutableAttributedString *buf = [[NSMutableAttributedString alloc] initWithString:@""];
	[self willChangeValueForKey:@"text"];
	@try {
		while(as = [e nextObject]) {
			[buf appendAttributedString:as];
		}
		@synchronized(self) {
			[_text appendAttributedString:buf];
			[[(ChatView *)[self view] textStorage] appendAttributedString:buf];
		}
	}
	@catch (id ue) {
		NSLog(@"Encountered an exception while updating chat text: %@.",ue);
	}
	[self didChangeValueForKey:@"text"];
	[buf release];
	
	if(newestmy) {
		[[newestmy date] retain];
		[_lastMe release];
		_lastMe = [newestmy date];
	}
	
	if(scroll)
		[(ChatView *)[self view] scrollToBottom];
		
	[self refresh];
}

- (void)refresh {
	[super refresh];
	[NSApp updateSourceListElement:[ChatsElement elementChatsRoot]];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
	if(object==[self chat]) {
		int ob = (int)context;
		switch(ob) {
			case ObserveJoined:
				[self appendInformationStatement:[NSString stringWithFormat:@"%@ joined.",[[[self chat] peerJoined] name]]];
				break;
			case ObserveParted:
				[self appendInformationStatement:[NSString stringWithFormat:@"%@ left.",[[[self chat] peerParted] name]
				]];
				break;
			default:
				[self performSelector:@selector(update) withObject:nil afterDelay:0.0];
		}
	}
	else if([self isSelected]) {
		int kind = [[change objectForKey:NSKeyValueChangeKindKey] intValue];
		switch(kind) {
			NSKeyValueChangeRemoval:
			NSKeyValueChangeInsertion:
			NSKeyValueChangeReplacement: {
				[self willChange:[[change objectForKey:NSKeyValueChangeKindKey] intValue] valuesAtIndexes:[change objectForKey:NSKeyValueChangeIndexesKey] forKey:@"participants"];
				[self didChange:[[change objectForKey:NSKeyValueChangeKindKey] intValue] valuesAtIndexes:[change objectForKey:NSKeyValueChangeIndexesKey] forKey:@"participants"];
				break;
			}
			default:
				[self willChangeValueForKey:@"participants"];
				[self didChangeValueForKey:@"participants"];
		}
	}
}


- (NSString *)name { 
	Chat *_chat = [self chat];
	return [self isRequestingAttention]?[NSString stringWithFormat:@"%@ (%d)",[_chat name],[_chat countOfStatementsNew]]:[_chat name]; 
}

- (NSAttributedString *)nameAttributed { 
	BOOL ajoined = [[[[[NSApp engine] chatsController] chatsJoined] mutableSet] containsObject:[self chat]];
	NSAttributedString *na = [super nameAttributed];
	if(ajoined==NO && [[self chat] type]==ChatTypeRoom) {
		NSDictionary *atts = [na attributesAtIndex:0 effectiveRange:nil];
		NSString *str = [na string];
		NSMutableDictionary *ma = [[NSMutableDictionary alloc] initWithDictionary:atts];
		[ma setObject:[NSColor grayColor] forKey:NSForegroundColorAttributeName];
		na = [[[NSAttributedString alloc] initWithString:str attributes:ma] autorelease];
		[ma release];
	}
	return na;
}

- (NSString *)titleWindow { return [[self chat] type]==ChatTypePrivate?[@"Chat with " stringByAppendingString:[[self chat] name]]:[@"Chat in " stringByAppendingString:[[self chat] name]]; }
- (NSImage *)icon { return [[self chat] icon]; }
- (BOOL)isRequestingAttention {
	return ([[self chat] countOfStatementsNew] > 0) && [self isSelected]==NO;
}
@end

#pragma mark -

@implementation ChatsElement
- (id)initWithView:(NSView *)view expandable:(BOOL)expand {
	if(self = [super initWithView:view]) {
		expandable = expand;
		if(expand && ElementChatsParent==nil)
			ElementChatsParent = self;
		children = [[NSMutableArray alloc] init];
	}
	return self;
}

- (void)dealloc {
	if(ElementChatsParent == self)
		ElementChatsParent = nil;
	[children release];
	[super dealloc];
}

- (NSString *)name { 
	if(expandable) {
		NSString *str = @"Chats";
		if([self isRequestingAttention]) {
			ChatElement *ce;
			NSEnumerator *e = [[self children] objectEnumerator];
			unsigned total = 0;
			while(ce = [e nextObject])
				total +=[[ce chat] countOfStatementsNew];
			return [str stringByAppendingFormat:@" (%d)",total];
		}
		else return str;
	}
	else return @"Chat Rooms"; 
}
- (NSImage *)icon { return expandable?[NSImage imageNamed:@"sourcelistChat"]:[NSImage imageNamed:@"sourcelistChatRoom"]; }

- (unsigned)count { return [[[[NSApp engine] chatsController] chatsInside] count]; }
- (BOOL)expandable { return expandable && [self count]!=0; }

- (BOOL)isRequestingAttention { 
	BOOL want = NO;
	if(expandable && [self isSelected]==NO) {
		NSEnumerator *e = [[self children] objectEnumerator];
		id<SourceListElement> o;
		while(o = [e nextObject]) {
			if([o isSelected])
				return NO;
			if([o isRequestingAttention])
				want = YES;
		}
	}
	return want;
}

- (NSArray *)children { 
	BOOL change = NO;
	if([self count] != [children count]) {
		SolarSeekApplication *ssa = NSApp;
		NSEnumerator *e = [[[[[NSApp engine] chatsController] chatsInside] values] objectEnumerator];
		NSMutableArray *ma = [NSMutableArray array];
		Chat *chat;
		NSImage *image = [NSImage imageNamed:@"sourcelistChat"];	//fejta: TODO: new image for group chats.!!
		while(chat = [e nextObject]) {
			ChatElement *ce = [[[ChatElement alloc] initWithView:[chat type]==ChatTypeRoom?[(ChatsView *)[self view]nibViewGroupChat]:[(ChatsView *)[self view] nibViewSingleChat] 
													chat:chat 
													icon:[chat type]==ChatTypeRoom?image:nil] autorelease];
			[ma addObject:ce];
			if([children containsObject:ce])
				continue;
			else {
				[children addObject:ce];
			}
		}
		if([ma count] != [children count]) {
			int i;
			for(i=0;i<[children count];i++) {
				ChatElement *ce = [children objectAtIndex:i];
				unsigned j = [ma indexOfObject:ce];
				if(j==NSNotFound) {
					if([ce isSelected])
						change = YES;
					[children removeObjectAtIndex:i--];
				}
				else
					[ma removeObjectAtIndex:j];
			}
		}
	}
	if(change) {
		[NSApp updateSourceList];
		[NSApp selectSourceListElement:self];
	}
	return children;
}

- (ChatElement *)elementForChat:(Chat *)chat {
	NSEnumerator *e = [[self children] objectEnumerator];
	ChatElement *ce;
	while(ce = [e nextObject]) {
		if(chat==[ce chat] || [[ce chat] isEqual:chat]) {
			return ce;
		}
	}
	return nil;
}
			
+ (ChatsElement *)elementChatsRoot { 
	return ElementChatsParent; 
}

+ (ChatElement *)elementForChat:(Chat *)chat { return [[self elementChatsRoot] elementForChat:chat]; }

- (void)selectChat:(Chat *)chat {
	[NSApp expandSourceListElement:self];
	[NSApp selectSourceListElement:[self elementForChat:chat]];
}

+ (void)scheduleSelectChat:(Chat *)chat {
	[[self elementChatsRoot] performSelector:@selector(selectChat:) withObject:chat afterDelay:0.05];
}

@end

